<?php
/**
 *  Yz_Action_Abstract
 */
abstract class Yz_Action_Abstract
    extends Yz_Class
    implements Yz_Action_Interface
{


    /** The count of items
     *  @var bool
     */
    protected $count = null;

    /** the model the action belongs to
     *  @var Yz_Model_Interface
     */
    protected $model;

    /** the SQL statement
     *  @var Yz_SQL_Statement
     */
    protected $statement;
    /**
     *  @var array( alias: tableClause )
     */
    protected $tables = array();


    /************************************
     *          Public methods          *
     ************************************/

    /**
     *  @param Yz_Model_Interface $model
     *  @return ignored
     */
    public function __construct(Yz_Model_Interface $model)
    {
        $this->model = $model;
    }

    /** Execute the action
     *  @return this
     *  @throw StateError if trying to perform same action again
     *  @throw NotFoundError if no items affected
     */
    public function perform()
    {
        debug();
        if (null !== $this->count)
            throw StateError::_new_();
        $this->count = $this
            ->verifyArguments()
            ->getStatement()
            ->execute()
            ->count()
        ;
        if (! $this->count)
            throw NotFoundError::_new_();
        return $this;
    }


    /**
     *  get the number of affected items/records [fetched,updated,created,deleted]
     *  @return int
     *  @throw StateError if count is null
     */
    public function count()
    {
        if (null === $this->count)
            throw StateError::_new_();
        return $this->count;
    }



    /************************************
     *        protected methods         *
     ************************************/


    /** Verify any arguments before building query
     *  virtual method
     *  @return this
     *  @throw StateError
     */
    protected function verifyArguments()
    {
        debug();
        return $this;
    }

    /**
     *  try to get the associated statement, throw exception if it is not initialized
     *  @return SQL Statement
     *  @throws StateError
     */
    protected function getStatement()
    {
        $st =& $this->statement;
        if (! $st)
            $st = $this->buildStatement();
        $this->applyArguments($st);
        return $st;
    }

    /**
     *  Build the common part of the SQL statement
     *  @abstract
     *  @return Yz_SQL_Statement
     */
    protected function buildStatement()
    {
        throw NotImplementerError::_new_();
    }

    /**
     *  Apply the arguments to the SQL statement
     *  @param Yz_SQL_Statement $sql
     *  @return this
     */
    protected function applyArguments(Yz_SQL_Statement $sql)
    {
        debug();
        return $this;
    }



    /** Inspect a model and determine what (fields) and from where (tables) will be selected
     *  @return Yz_Array fields
     */
    protected function inspectModel()
    {
        debug();
        $model = $this->model;
        $alias = $model->getName();
        $this->tables[$alias] = $model->getSourceName();
        return $this->doInspectModel($this->model, $alias);
    }


    /**
     *  Inspect model's property and create a field clause or
     *      recursively inspect referenced model
     *  @param Yz_Model_Interface $model
     *  @param string $name
     *  @param Yz_Model_Field_Interface $prop
     *  @return this
     */
    protected function inspectField(Yz_Model_Interface $model, $alias, $propName, Yz_Model_Field_Interface $prop)
    {
        return $this;
    }



    /** Inspect a model and determine what (fields) and from where (tables) will be selected
     *  @param Yz_Model_Interface $model
     *  @param string $alias
     *  @return this
     */
    protected function doInspectModel(Yz_Model_Interface $model, $alias)
    {
        debug();
        $parentModel = $model->getParentModel();

        // identify model's fields
        foreach ($model as $propName => $prop)
            if (! $parentModel or ! $parentModel->offsetExists($propName))
                $this->inspectField($model, $alias, $propName, $prop);
        return $this;
    }


}
